package com.evil.file.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.evil.common.account.util.LoginUtil;
import com.evil.common.core.enums.BaseEnum;
import com.evil.common.core.exception.BusinessException;
import com.evil.common.core.util.ServletUtils;
import com.evil.common.core.util.StreamUtil;
import com.evil.common.file.constant.OssConstant;
import com.evil.common.file.dto.FileRespDTO;
import com.evil.common.file.dto.UploadReqDTO;
import com.evil.common.file.dto.UploadedRespDTO;
import com.evil.common.file.dto.download.history.AddFileDownloadHistoryReqDTO;
import com.evil.common.file.enums.BusTypeEnum;
import com.evil.common.file.util.ExportUtil;
import com.evil.common.oss.service.FileHandleService;
import com.evil.common.oss.util.FileUtil;
import com.evil.file.api.RemoteFileService;
import com.evil.file.mapper.FileMapper;
import com.evil.file.pojo.entity.File;
import com.evil.file.service.FileDownloadHistoryService;
import com.evil.file.service.FileService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.function.Consumer;

/**
 * 文件表
 *
 * @author liyang
 * @date 2022-08-08 00:00
 */
@Slf4j
@RestController
@RequiredArgsConstructor
public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements FileService, RemoteFileService {

    @Value("${spring.application.name}")
    private String applicationName;

    @Value("${spring.profiles.active}")
    private String environment;

    private final LoginUtil loginUtil;

    private final FileHandleService fileHandleService;

    private final FileDownloadHistoryService fileDownloadHistoryService;

    private final FileLogServiceImpl fileLogService;

    /**
     * 新增文件记录 若是头像类型则 修改用户头像信息
     *
     * @param uploadReqDTO 上传信息
     * @return FileRespDTO
     */
    @Override
    public FileRespDTO uploadFile(UploadReqDTO uploadReqDTO) {
        uploadReqDTO.setUserId(loginUtil.getParamUserId(uploadReqDTO.getUserId()));
        uploadReqDTO.setEnterpriseId(loginUtil.getParamEnterpriseId(uploadReqDTO.getEnterpriseId()));

        BusTypeEnum busTypeEnum = BusTypeEnum.findById(uploadReqDTO.getType());
        UploadedRespDTO upload = fileHandleService.upload(uploadReqDTO.getFile(), uploadReqDTO.getEnterpriseId(),
                busTypeEnum, uploadReqDTO.getAllowMaxSize(), uploadReqDTO.getAllowContentType(), uploadReqDTO.getIsPreview());

        File file = BeanUtil.copyProperties(upload, File.class);
        file.setEnterpriseId(uploadReqDTO.getEnterpriseId());
        file.setBelongId(uploadReqDTO.getUserId());
        file.setFileUrl(upload.getUrl());
        file.setFilePath(upload.getFilePath());
        file.setFileMd5(upload.getFileMD5());
        file.setBusType(uploadReqDTO.getType());
        file.setFileCode(FileUtil.getFileCode(uploadReqDTO.getUserId()));
        fileLogService.saveAndCheck(this.baseMapper, File::getFileId, file);

        return this.fileToFileRespDTO(file);
    }

    /**
     * 获取文件服务Host
     *
     * @return String
     */
    @Override
    public String getFileServerHost() {
        return fileHandleService.getFileServerHost();
    }

    /**
     * 获取文件URL
     *
     * @param fileId fileId
     * @return Response
     */
    @Override
    public String getFileUrlByFileId(Long fileId) {
        return this.fileToFileRespDTO(this.baseMapper.findById(fileId)).getFileUrl();
    }

    /**
     * 获取文件URL
     *
     * @param fileCode fileCode
     * @return Response
     */
    @Override
    public String getFileUrlByFileCode(String fileCode) {
        return this.fileToFileRespDTO(this.baseMapper.findByCode(fileCode)).getFileUrl();
    }

    /**
     * 获取文件流
     *
     * @param fileId fileId
     * @return Response
     */
    @Override
    public byte[] getFileByteByFileId(Long fileId) {
        return this.getFileByteByFileBase(this.findBaseByFileId(fileId));
    }

    /**
     * 获取文件流
     *
     * @param fileCode fileCode
     * @return Response
     */
    @Override
    public byte[] getFileByteByFileCode(String fileCode) {
        return this.getFileByteByFileBase(this.findBaseByFileCode(fileCode));
    }

    /**
     * 获取文件流
     *
     * @param fileRespDTO fileRespDTO
     * @return Response
     */
    @Override
    public byte[] getFileByteByFileBase(FileRespDTO fileRespDTO) {
        // 创建文件下载记录
        this.createDownloadHistory(fileRespDTO, OssConstant.SYSTEM_DEFAULT_SIGNATURE);
        return this.getFileByte(fileRespDTO);
    }

    /**
     * 获取文件流
     *
     * @param fileRespDTO fileRespDTO
     * @return Response
     */
    @Override
    public byte[] getFileByteByFileBase_system(FileRespDTO fileRespDTO) {
        return this.getFileByte(fileRespDTO);
    }

    /**
     * 根据文件id获取文件
     *
     * @param fileId fileId
     * @return FileRespDTO Optional
     */
    @Override
    public Optional<FileRespDTO> isExistBaseByFileId(Long fileId) {
        return this.baseMapper.isExistById(fileId).map(this::fileToFileRespDTO);
    }

    /**
     * 根据文件id获取文件（不存在抛异常）
     *
     * @param fileId fileId
     * @return FileRespDTO
     */
    @Override
    public FileRespDTO findBaseByFileId(Long fileId) {
        return this.fileToFileRespDTO(this.baseMapper.findById(fileId));
    }

    /**
     * 根据文件code获取文件
     *
     * @param fileCode fileCode
     * @return FileRespDTO Optional
     */
    @Override
    public Optional<FileRespDTO> isExistBaseByFileCode(String fileCode) {
        return this.baseMapper.isExistByCode(fileCode).map(this::fileToFileRespDTO);
    }

    /**
     * 根据文件code获取文件（不存在抛异常）
     *
     * @param fileCode fileCode
     * @return FileRespDTO
     */
    @Override
    public FileRespDTO findBaseByFileCode(String fileCode) {
        return this.fileToFileRespDTO(this.baseMapper.findByCode(fileCode));
    }

    /**
     * 根据文件ids获取文件
     *
     * @param fileIds fileIds
     * @return FileRespDTO 列表
     */
    @Override
    public List<FileRespDTO> findBaseByFileIds(Collection<Long> fileIds) {
        if (CollectionUtil.isEmpty(fileIds)) {
            return new ArrayList<>();
        }
        return this.fileToFileRespDTO(this.baseMapper.findByIds(fileIds, true));
    }

    /**
     * 根据文件codes获取文件
     *
     * @param fileCodes fileCodes
     * @return FileRespDTO 列表
     */
    @Override
    public List<FileRespDTO> findBaseByFileCodes(Collection<String> fileCodes) {
        return this.fileToFileRespDTO(this.baseMapper.findByCodes(fileCodes, true));
    }

    /**
     * 根据文件ids获取文件
     *
     * @param fileIds fileIds
     * @return FileRespDTO Map
     */
    @Override
    public Map<Long, FileRespDTO> findBaseMapByFileIds(Collection<Long> fileIds) {
        return StreamUtil.toMapK(this.findBaseByFileIds(fileIds), FileRespDTO::getFileId);
    }

    /**
     * 根据文件codes获取文件
     *
     * @param fileCodes fileCodes
     * @return FileRespDTO Map
     */
    @Override
    public Map<String, FileRespDTO> findBaseMapByFileCodes(Collection<String> fileCodes) {
        return StreamUtil.toMapK(this.findBaseByFileCodes(fileCodes), FileRespDTO::getFileCode);
    }

    /**
     * 获取下载url
     *
     * @param fileCode  文件code
     * @param timestamp 过期时间
     * @param token     登录人token
     * @param signature 签名
     * @return String
     */
    @Override
    public String getDownloadUrl(String fileCode, Long timestamp, String token, String signature) {
        FileUtil.checkFileSignature(fileCode, timestamp, token, signature);
        FileRespDTO fileRespDTO = this.findBaseByFileCode(fileCode);

        // 创建文件下载记录
        this.createDownloadHistory(fileRespDTO, signature);
        return fileHandleService.getDownloadUrl(fileRespDTO.getFilePath(), fileRespDTO.getFileName(), new Date(timestamp));
    }

    @Override
    public void download(String fileCode, Long timestamp, String token, String signature, HttpServletResponse response) {
        FileUtil.checkFileSignature(fileCode, timestamp, token, signature);
        FileRespDTO fileRespDTO = this.findBaseByFileCode(fileCode);
        byte[] bytes = this.getFileByteByFileBase(fileRespDTO);

        Consumer<OutputStream> export = os -> {
            try {
                response.getOutputStream().write(bytes);
            } catch (IOException e) {
                throw new BusinessException(BaseEnum.RUN_ERROR);
            }
        };
        ExportUtil.export(export, response, fileRespDTO.getFileName());
    }

    /**
     * 转换基础文件信息
     *
     * @param file file
     * @return FileRespDTO
     */
    private FileRespDTO fileToFileRespDTO(File file) {
        String host = this.getFileServerHost();
        return this.fileToFileRespDTO(file, host);
    }

    /**
     * 转换基础文件信息
     *
     * @param files files
     * @return FileRespDTO
     */
    private List<FileRespDTO> fileToFileRespDTO(Collection<File> files) {
        String host = this.getFileServerHost();
        return StreamUtil.transListT(files, f -> this.fileToFileRespDTO(f, host));
    }

    /**
     * 转换基础文件信息
     *
     * @param file file
     * @param host host
     * @return FileRespDTO
     */
    private FileRespDTO fileToFileRespDTO(File file, String host) {
        return this.init(file, host, BeanUtil.copyProperties(file, FileRespDTO.class));
    }

    /**
     * 文件url处理
     *
     * @param file        file
     * @param host        host
     * @param fileRespDTO fileRespDTO
     * @return FileRespDTO
     */
    private FileRespDTO init(@NotNull(message = "file not null") File file,
                             @NotBlank(message = "host not null") String host,
                             FileRespDTO fileRespDTO) {
        BusTypeEnum busTypeEnum = BusTypeEnum.findById(file.getBusType());
        String url = file.getFileUrl();
        if (busTypeEnum.isPrivate()) {
            // 私有文件，走服务器，验签后获取/下载
            url = FileUtil.generateFileUrl(host, file.getFileCode(), loginUtil.getLoginToken());
        }
        fileRespDTO.setFileUrl(url);
        return fileRespDTO;
    }

    /**
     * 创建文件下载记录
     *
     * @param fileRespDTO fileRespDTO
     */
    private void createDownloadHistory(FileRespDTO fileRespDTO, String signature) {
        // 创建文件下载记录
        AddFileDownloadHistoryReqDTO addFileDownloadHistory = new AddFileDownloadHistoryReqDTO();
        HttpServletRequest request = ServletUtils.getRequest();
        addFileDownloadHistory.setClientIp(request.getRemoteAddr());
        addFileDownloadHistory.setDownloadKey(signature);
        addFileDownloadHistory.setServerHost(request.getRemoteHost());
        addFileDownloadHistory.setServerIp(request.getLocalAddr());
        addFileDownloadHistory.setServiceId(applicationName);
        addFileDownloadHistory.setEnv(environment);
        addFileDownloadHistory.setFileId(fileRespDTO.getFileId());
        fileDownloadHistoryService.add(addFileDownloadHistory);
    }

    /**
     * 获取文件 Response
     *
     * @param fileRespDTO fileRespDTO
     * @return byte
     */
    private byte[] getFileByte(FileRespDTO fileRespDTO) {
        InputStream is = fileHandleService.getDownloadInputStream(fileRespDTO.getFilePath(), fileRespDTO.getFileName(), new Date());
        return IoUtil.readBytes(is);
    }
}

